using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
using System.Linq;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;

namespace XnaGuiContentPipeline
{
    [ContentImporter(".uitheme", DisplayName = "CellarDoor UiThemeImporter", DefaultProcessor = "CellarDoor UiThemeImporter")]
    public class UiThemeImporter : ContentImporter<UiThemeContent>
    {
        public override UiThemeContent Import(string filename, ContentImporterContext context)
        {
            XDocument doc = XDocument.Load(filename);
            UiThemeContent content = new UiThemeContent();
            content.Identity = new ContentIdentity(filename);

            TextureReferenceDictionary textures = ParseTextures(doc, content.Identity, context);
            content.Fonts = ParseFonts(doc, content.Identity, context);
            content.Sprites = ParseSprites(doc, textures, content.Identity, context);
            content.Elements = ParseElements(doc, textures, content.Sprites, content.Identity, context);
            
            return content;
        }

        #region Textures
        private TextureReferenceDictionary ParseTextures(
            XDocument document, ContentIdentity identity, ContentImporterContext context)
        {
            TextureReferenceDictionary textures = new TextureReferenceDictionary();
            foreach (XElement element in document.Root.Element("Textures").Elements("Texture"))
            {
                string name = (string)element.Attribute("name");
                string reference = element.Value;

                if (string.IsNullOrEmpty(name))
                {
                    throw new InvalidContentException("Texture element must have a non empty name attribute");
                }

                textures.Add(name.ToLowerInvariant(), new ExternalReference<TextureContent>(reference, identity));
            }
            return textures;
        }
        #endregion

        #region Fonts
        private List<ThemeFontContent> ParseFonts(
            XDocument doc, ContentIdentity identity, ContentImporterContext context)
        {
            List<ThemeFontContent> fonts = new List<ThemeFontContent>();

            foreach (XElement elem in doc.Root.Element("Fonts").Elements("Font"))
            {
                string name = (string)elem.Attribute("name");
                string reference = elem.Value;
                string resolution = (string)elem.Attribute("resolution");

                if (string.IsNullOrEmpty(name))
                {
                    throw new InvalidContentException("Font element must have a non empty name attribute");
                }

                ThemeFontContent content = new ThemeFontContent();
                content.Name = name.ToLowerInvariant();
                content.Font = new ExternalReference<SpriteFontContent>(reference, identity);
                content.Resolution = ParseResolution(resolution);
                fonts.Add(content);
            }
            return fonts;
        }
        #endregion

        #region Sprites
        private Dictionary<string, ThemeSpriteContent> ParseSprites(
            XDocument document, TextureReferenceDictionary textures, 
            ContentIdentity identity, ContentImporterContext context)
        {
            Dictionary<string, ThemeSpriteContent> contents = new Dictionary<string, ThemeSpriteContent>();
            foreach (XElement list in document.Root.Elements("Sprites"))
            {
                string defaultTextureName = (string)list.Attribute("texture") ?? "";
                defaultTextureName = defaultTextureName.ToLowerInvariant();

                ExternalReference<TextureContent> defaultTexture = null;
                textures.TryGetValue(defaultTextureName, out defaultTexture);

                foreach (XElement item in list.Elements("Sprite"))
                {
                    ThemeSpriteContent content = new ThemeSpriteContent();
                    content.Name = (string)item.Attribute("name");
                    content.Name = content.Name.ToLowerInvariant();
                    content.Rectangle = ParseRectangle(item.Value);
                    content.Texture = defaultTexture;
                    contents.Add(content.Name, content);

                    if (item.Attribute("texture") != null)
                    {
                        string textureName = (string)item.Attribute("texture") ?? "";
                        textureName = textureName.ToLowerInvariant();

                        ExternalReference<TextureContent> texture = null;
                        textures.TryGetValue(textureName, out texture);
                        content.Texture = texture;
                    }

                    if (string.IsNullOrEmpty(content.Name))
                    {
                        throw new InvalidContentException(String.Format(
                            "The element '{0}' must have a non empty name attribute.", item.ToString()), identity);
                    }
                    if (content.Texture == null)
                    {
                        throw new InvalidContentException(String.Format(
                            "The element '{0}' or its parent must have a non empty and valid texture attribute.", item.ToString()), identity);
                    }
                }
            }
            return contents;
        }
        #endregion

        #region Elements
        private List<ThemeElementContent> ParseElements(
            XDocument doc, TextureReferenceDictionary textures, 
            Dictionary<string, ThemeSpriteContent> sprites,
            ContentIdentity identity, ContentImporterContext context)
        {
            List<ThemeElementContent> content = new List<ThemeElementContent>();

            foreach (XElement elem in doc.Root.Element("Elements").Elements("Element"))
            {
                ThemeElementContent elementContent = new ThemeElementContent();
                content.Add(elementContent);

                elementContent.Name = ((string)elem.Attribute("name") ?? "").ToLowerInvariant();
                elementContent.Resolution = ParseResolution((string)elem.Attribute("resolution"));

                string defaultTextureName = (string)elem.Attribute("texture") ?? "";
                defaultTextureName = defaultTextureName.ToLowerInvariant();
                ExternalReference<TextureContent> defaultTexture = null;
                textures.TryGetValue(defaultTextureName, out defaultTexture);

                elementContent.States = new List<ThemeSpriteContent>();
                foreach (XElement stateElem in elem.Elements("State"))
                {
                    ThemeSpriteContent stateContent = new ThemeSpriteContent();
                    elementContent.States.Add(stateContent);

                    stateContent.Name = ((string)stateElem.Attribute("name") ?? "").ToLowerInvariant();

                    if (stateElem.Attribute("sprite") != null)
                    {
                        string spriteName = stateElem.Attribute("sprite").Value.ToLowerInvariant();
                        ThemeSpriteContent sprite = null;

                        if (!sprites.TryGetValue(spriteName, out sprite))
                        {
                            throw new InvalidContentException(String.Format(
                                "State element '{0}' in '{1}' contains an unknown sprite reference '{2}'",
                                stateElem.ToString(), elem.Name, spriteName),
                                identity);
                        }

                        stateContent.Rectangle = new TiledRectangleContent();
                        stateContent.Rectangle.rectangle1 = sprite.Rectangle.rectangle1;
                        stateContent.Rectangle.rectangle2 = sprite.Rectangle.rectangle2;
                        stateContent.Resolution = sprite.Resolution;
                        stateContent.Texture = sprite.Texture;
                        stateContent.TextureContent = sprite.TextureContent;
                        
                        continue;
                    }

                    stateContent.Texture = defaultTexture;
                    stateContent.Rectangle = ParseRectangle(stateElem.Value);

                    // override texture
                    if (stateElem.Attribute("texture") != null)
                    {
                        string textureName = (string)stateElem.Attribute("texture");
                        textures.TryGetValue(textureName, out stateContent.Texture);
                    }

                    if (stateContent.Texture == null)
                    {
                        throw new InvalidContentException(String.Format(
                            "State element '{0}' or its parent '{1}' must have a non empty and valid texture attribute",
                            stateElem.ToString(), elem.Name),
                            identity);
                    }
                }
            }

            return content;
        }
        #endregion

        private int ParseResolution(string resolution)
        {
            if (!string.IsNullOrEmpty(resolution))
            {
                string[] data = resolution.Split(' ');

                int width = 0;
                int height = 0;
                if (!int.TryParse(data[0], out width))
                {
                    throw new InvalidContentException("invalid resolution attribute");
                }
                if (!int.TryParse(data[1], out height))
                {
                    throw new InvalidContentException("invalid resolution attribute");
                }

                return width * height;
            }

            return 0;
        }

        private TiledRectangleContent ParseRectangle(string value)
        {
            string[] sValue = value.Split(' ');
            List<int> data = new List<int>();
            foreach (var item in sValue)
            {
                if (String.IsNullOrEmpty(item))
                {
                    continue;
                }

                int integer = 0;
                if (Int32.TryParse(item, out integer))
                {
                    data.Add(integer);
                }
                else
                {
                    throw new InvalidContentException(String.Format("Invalid Rectangle data {0}", value));
                }
            }

            if (data.Count == 4)
            {
                TiledRectangleContent tc = new TiledRectangleContent();

                tc.rectangle1.X = data[0];
                tc.rectangle1.Y = data[1];
                tc.rectangle1.Width = 0;
                tc.rectangle1.Height = 0;

                tc.rectangle2.X = data[2] + 1; // makes the pixel "inclusive"
                tc.rectangle2.Y = data[3] + 1; // makes the pixel "inclusive"
                tc.rectangle2.Width = 0;
                tc.rectangle2.Height = 0;

                return tc;
            }
            if (data.Count == 5)
            {
                TiledRectangleContent tc = new TiledRectangleContent();

                tc.rectangle1.X = data[0];
                tc.rectangle1.Y = data[1];
                tc.rectangle1.Width = data[4];
                tc.rectangle1.Height = data[4];

                tc.rectangle2.X = data[2] + 1 - data[4];
                tc.rectangle2.Y = data[3] + 1 - data[4];
                tc.rectangle2.Width = data[4];
                tc.rectangle2.Height = data[4];

                return tc;
            }
            if (data.Count == 6)
            {
                TiledRectangleContent tc = new TiledRectangleContent();

                tc.rectangle1.X = data[0];
                tc.rectangle1.Y = data[1];
                tc.rectangle1.Width = data[5];
                tc.rectangle1.Height = data[4];

                tc.rectangle2.X = data[2] + 1 - data[5];
                tc.rectangle2.Y = data[3] + 1 - data[4];
                tc.rectangle2.Width = data[5];
                tc.rectangle2.Height = data[4];

                return tc;
            }
            else if (data.Count == 8)
            {
                TiledRectangleContent tc = new TiledRectangleContent();

                tc.rectangle1.X = data[0];
                tc.rectangle1.Y = data[1];
                tc.rectangle1.Width = data[2];
                tc.rectangle1.Height = data[3];

                tc.rectangle2.X = data[4];
                tc.rectangle2.Y = data[5];
                tc.rectangle2.Width = data[6];
                tc.rectangle2.Height = data[7];

                return tc;
            }
            else
            {
                throw new InvalidContentException(String.Format("Invalid Rectangle data {0}", value));
            }
        }
    }
}
